use crate::errors::{MyError, MyResult};
use crate::utils::config::MIXINS_FOLDER_NAME;
use crate::utils::{common, git};
use crate::vue_parser::VueParser;
use serde::Deserialize;
use std::path::Path;

#[derive(Deserialize, Debug, Clone)]
pub struct ProjectBase {
    pub comment: Option<String>,
    pub git: String,
    pub tag: String,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PageItem {
    pub key: String,
    pub vue_path: String,
}

pub struct PageStagingItems {
    pub items: Vec<PageStagingItem>,
}

#[derive(Debug, PartialEq)]
pub struct PageStagingItem {
    pub key: String,
    pub vue_path: String,
    pub module_path: String,
    pub mixins_folder_path: String,
    pub mixins_components_staging_path: String,
    pub mixins_components_path: String,
    pub mixins_methods_staging_path: String,
    pub module_methods_path: String,
}

impl PageStagingItem {
    pub fn try_from(
        item: &PageItem,
        module_folder_name: &str,
        mixins_folder_name: &str,
    ) -> MyResult<Self> {
        let module_path = &common::path_join(&module_folder_name, &item.vue_path)?;
        let mixins_path = &common::path_join(&mixins_folder_name, &item.vue_path)?;
        let mixins_folder_path = Path::new(&mixins_path)
            .parent()
            .map(|x| x.to_str())
            .ok_or_else(|| MyError::Msg("module_folder_path error".to_string()))?
            .ok_or_else(|| MyError::Msg("module_folder_path 1 error".to_string()))?;
        Ok(Self {
            key: item.key.clone(),
            vue_path: item.vue_path.clone(),
            module_path: module_path.to_owned(),
            mixins_folder_path: mixins_folder_path.to_string(),
            mixins_components_staging_path: common::path_join(
                mixins_folder_path,
                "componentsMixins.staging",
            )?,
            mixins_components_path: common::path_join(mixins_folder_path, "componentsMixins.js")?,
            mixins_methods_staging_path: common::path_join(
                mixins_folder_path,
                "methodsMixins.staging",
            )?,
            module_methods_path: common::path_join(mixins_folder_path, "methodsMixins.js")?,
        })
    }
}

impl TryFrom<Pages> for PageStagingItems {
    type Error = MyError;
    fn try_from(val: Pages) -> std::result::Result<Self, Self::Error> {
        let project_name = &git::get_default_git_project_name(&val.git).unwrap();
        let mut page_staging_items: Vec<PageStagingItem> = Vec::with_capacity(val.items.len());

        for item in val.items {
            page_staging_items.push(PageStagingItem::try_from(
                &item,
                project_name,
                MIXINS_FOLDER_NAME,
            )?);
        }
        Ok(PageStagingItems {
            items: page_staging_items,
        })
    }
}
impl PageStagingItems {
    pub fn find(&self, page_key: &str) -> Option<&PageStagingItem> {
        self.items.iter().find(|item| item.key == page_key)
    }
}

#[derive(Deserialize, Debug, Clone)]
pub struct Pages {
    pub root: String,
    pub git: String,
    pub tag: String,
    pub items: Vec<PageItem>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ComponentItem {
    pub comment: Option<String>,
    pub key: String,
    pub vue_path: String,
    pub component_name: String,
}
pub struct ComponentStagingItem {
    pub key: String,
    pub vue_path: String,
    pub component_name: String,
    pub module_path: String,
    pub events: Vec<(String, String)>,
}

impl ComponentStagingItem {
    fn build_component_events(&self) -> String {
        self.events
            .iter()
            .map(|(event, event_handler)| format!("@{}=\"{}\"", event, event_handler))
            .collect::<Vec<String>>()
            .join(" ")
    }
    pub fn build_component(&self) -> String {
        format!(
            "<{} {}/>",
            &self.component_name,
            self.build_component_events()
        )
    }
    pub fn build_component_staging(&self) -> String {
        format!(
            r#"
        {{
           "import": "import {} from '@/{}'",
           "name": "{}"
        }},"#,
            &self.component_name, &self.module_path, &self.component_name
        )
    }
    pub fn build_events_staging(&self) -> String {
        format!(
            "{},",
            self.events
                .iter()
                .map(|(_, event_handler)| format!(
                    r#"
        {{
          "event": "{}",
          "code": "{} () {{}}"

        }}"#,
                    event_handler, event_handler
                ))
                .collect::<Vec<String>>()
                .join(",")
        )
    }
}
pub struct ComponentStagingItems {
    items: Vec<ComponentStagingItem>,
}
impl ComponentStagingItems {
    pub fn find(&self, component_key: &str) -> Option<&ComponentStagingItem> {
        self.items.iter().find(|item| item.key == component_key)
    }
}
impl TryFrom<Components> for ComponentStagingItems {
    type Error = MyError;
    fn try_from(val: Components) -> std::result::Result<Self, Self::Error> {
        let project_name = git::get_default_git_project_name(&val.git)?;
        let mut component_staging_items: Vec<ComponentStagingItem> =
            Vec::with_capacity(val.items.len());
        //
        //let file_content = &;
        //if let Some(script_info) = VueParser::get_script(file_content) {
        for item in val.items {
            let module_path = &common::path_join(&project_name, &item.vue_path)?;
            let events = &VueParser::get_script(&common::load_project_file(module_path)?)
                .ok_or_else(|| MyError::Msg("try from components script error".to_string()))?
                .events()
                .iter()
                .map(|event| {
                    (
                        event.clone(),
                        format!(
                            "{}_{}",
                            format!(
                                "{}{}",
                                &item.component_name.as_str()[0..1].to_lowercase(),
                                &item.component_name.as_str()[1..]
                            ),
                            event
                        ),
                    )
                })
                .collect::<Vec<(String, String)>>();
            component_staging_items.push(ComponentStagingItem {
                key: item.key.clone(),
                vue_path: item.vue_path.clone(),
                component_name: item.component_name.clone(),
                module_path: module_path.to_owned(),
                events: events.clone(),
            });
        }

        Ok(ComponentStagingItems {
            items: component_staging_items,
        })
    }
}
#[derive(Deserialize, Debug, Clone)]
pub struct Components {
    pub comment: Option<String>,
    pub git: String,
    pub tag: String,
    pub items: Vec<ComponentItem>,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ComposeAreaItem {
    pub page_key: String,
    pub template_area_key: String,
    pub component_key: String,
}
#[derive(Deserialize, Debug)]
pub struct ComposeAreas {
    pub comment: Option<String>,
    pub items: Vec<ComposeAreaItem>,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CliConfig {
    pub project_name: String,
    pub project_base: ProjectBase,
    pub pages: Pages,
    pub components: Components,
    pub compose_areas: ComposeAreas,
}
